﻿using System;
using System.Xml;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;

namespace MabiCommerce
{
    public class Item
    {
        public string Name { get; protected set;}
        public int Weight { get; protected set; }
        public int MaxQuantity { get; protected set; }
        public int NumberAvailable { get; set;}
        public int MerchantRating { get; protected set; }
        public Dictionary<Town, int> Profits { get; protected set; }
        public List<Town> Touched { get; protected set; }
        public Item(string itemName, int itemWeight, int itemMaxQty, int itemMechRating)
        {
            Name = itemName;
            Weight = itemWeight;
            MaxQuantity = itemMaxQty;
            NumberAvailable = -1;
            MerchantRating = itemMechRating;
            Profits = new Dictionary<Town, int>();
            Touched = new List<Town>();
        }
    }
    public class Path
    {
        public string Name { get; protected set; }
        public List<Town> Towns { get; protected set;}
        public int Length { get; protected set;}
        public Point MapLocation { get; protected set;}
        public Point MapSplitLeft { get; protected set;}
        public Point MapSplitRight { get; protected set; }
        public Bitmap Map { get; protected set; }
        public Path(string name, Town one, Town two, int time, Point loc, Point SplitLeft, Point SplitRight)
        {
            Name = name;
            Towns = new List<Town>();
            Towns.Add(one);
            Towns.Add(two);
            Length = time;
            MapLocation = loc;
            MapSplitLeft = SplitLeft;
            MapSplitRight = SplitRight;
            if (! Name.Equals("None") && loc.X >= 0)
            {
                Map = (Bitmap)Image.FromFile(@"data\map\" + name + ".png");
            }
        }
        public override string ToString()
        {
            return Towns[0].Name + " to " + Towns[1].Name + " in " + Length + " s";
        }
        public Town GetOtherTown(Town notMe)
        {
            return Towns.Find(new Predicate<Town>((t)=> { return t != notMe; }));
        }
    }
    public class Town
    {
        public string Name { get; protected set;}
        public List<Path> Paths { get; protected set; }
        public List<Item> Items { get; protected set; }
        public Point MapLocation { get; protected set; }
        public Bitmap Map { get; protected set; }
        public Town(string townName, List<Item> townItems, Point loc)
        {
            Name = townName;
            Paths = new List<Path>();
            Items = townItems;
            MapLocation = loc;
            if (loc.X >= 0)
                Map = (Bitmap)Image.FromFile(@"data\map\" + townName + ".png");
        }
        public override string ToString()
        {
            return Name + " - " + Paths.Count + " paths";
        }
    }
    public class Route
    {
        public List<Town> EndTowns { get; protected set; }
        public List<Town> Waypoints { get; protected set; }
        public int TotalTime { get; protected set; }
        public Route(Town End1, Town End2, int Time, params Town[] InBetween)
        {
            EndTowns = new List<Town>();
            Waypoints = new List<Town>();
            TotalTime = Time;
            EndTowns.Add(End1);
            EndTowns.Add(End2);
            Waypoints.AddRange(InBetween);
        }
        public override string ToString()
        {
            return string.Format("{0} to {1} in {2} seconds. Waypoints: {3}", EndTowns[0].Name, EndTowns[1].Name, TotalTime, string.Join(", ", Waypoints));
        }
    }
    public class Transport
    {
        public string Name  { get; protected set;}
        public int MaxCapacity  { get; protected set;}
        public int MaxWeight { get; protected set; }
        public double SpeedBoost { get; protected set; }
        public bool Required { get; protected set; }
        public Transport(string tName, int tCapacity, int tWeight, double tBoost, bool tRequired)
        {
            Name = tName;
            MaxCapacity = tCapacity;
            MaxWeight = tWeight;
            SpeedBoost = tBoost;
            Required = tRequired;
        }
    }

    public class CommerceRun : IComparable<CommerceRun>
    {
        public Route TheRoute { get; protected set; }
        public Transport TransportMode { get; protected set; }
        public Item TheItem { get; protected set; }
        public TimeSpan TotalTime { get { return new TimeSpan(0, 0, (int)(TheRoute.TotalTime / TransportMode.SpeedBoost)); } }
        public int Profit { get { return TheItem.Profits[destTown] * NumberOfItems; } }
        public double ProfitPerSecond { get { return Profit / TotalTime.TotalSeconds; } }
        public Town oTown { get; protected set; }
        public Town destTown { get; protected set;}
        public int NumberOfItems
        {
            get
            {
                int min = Math.Min((TransportMode.MaxWeight / TheItem.Weight), (TransportMode.MaxCapacity * TheItem.MaxQuantity));
                if (TheItem.NumberAvailable != -1)
                    min = Math.Min(min, TheItem.NumberAvailable);
                return min;
            }
        }

        public CommerceRun(Route route, Transport transport, Item item)
        {
            TheRoute = route;
            TransportMode = transport;
            TheItem = item;
            oTown = Erinn.Towns.Find(new Predicate<Town>((t) => { return t.Items.Contains(TheItem); }));
            destTown = TheRoute.EndTowns.Find(new Predicate<Town>((t) => { return t != oTown; }));
        }

        public int CompareTo(CommerceRun other)
        {
            double mPPS = this.ProfitPerSecond, oPPS = other.ProfitPerSecond;
            if (mPPS == oPPS)
            {
                if (this.TotalTime.TotalSeconds == other.TotalTime.TotalSeconds)
                    return 0;
                else if (this.TotalTime.TotalSeconds > other.TotalTime.TotalSeconds)
                    return -1;
                else
                    return 1;
            }
            else if (mPPS < oPPS)
                return -1;
            else
                return 1;
        }

        public override string ToString()
        {
            return TheRoute.ToString() + " carrying " + NumberOfItems + " " + TheItem.Name + "s with a " + TransportMode.Name + " for a profit of " + Profit + " in " + TotalTime + " which is a Profit/Second of: " + ProfitPerSecond; 
        }
    }
    static class Erinn
    {
        public static List<Town> Towns { get; private set; }
        public static List<Route> Routes { get; private set; }
        public static List<Path> Paths { get; private set; }
        public static List<Transport> AllTransports  { get; private set;}
        public static List<Transport> AvailableTransports { get; set; }

        private static Point parsePoint(string text)
        {
            return (Point)System.ComponentModel.TypeDescriptor.GetConverter(typeof(Point)).ConvertFromInvariantString(text);
        }

        public static void Initialize()
        {
            LoadInfo();
            AvailableTransports = new List<Transport>();
        }

        public static Route GetRouteBetween(Town t1, Town t2)
        {
            return Routes.Find(new Predicate<Route>((r) =>
                {
                    return r.EndTowns.Contains(t1) && r.EndTowns.Contains(t2);
                }
            ));
        }

        private static void LoadInfo()
        {
            try
            {
                loadTransports();
                loadTowns();
                loadPaths();
                System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();
                s.Start();
                calculateRoutes();
                s.Stop();
                System.Diagnostics.Debug.WriteLine("Found all routes in " + s.Elapsed);
            }
            catch (Exception ex)
            {
                if (ex is NullReferenceException || ex is XmlException || ex is InvalidCastException) //Unify XML errors
                    throw new Exception("Malformatted XML file.", ex);
                else
                    throw;
            }
        }
        private static void loadTransports()
        {
            AllTransports = new List<Transport>();
            XmlDocument transportXML = new XmlDocument();
            transportXML.Load(@"data\Transports.xml");
            XmlNode transportRoot = transportXML.DocumentElement;
            foreach (XmlNode transportNode in transportRoot.ChildNodes)
            {
                if (transportNode.NodeType == XmlNodeType.Comment)
                    continue;
                string Name = transportNode.Attributes["Name"].Value;
                int Capacity = int.Parse(transportNode.SelectSingleNode("Storage").InnerText);
                int Weight = int.Parse(transportNode.SelectSingleNode("Weight").InnerText);
                double Speed = double.Parse(transportNode.SelectSingleNode("Speed").InnerText);
                bool Required = bool.Parse(transportNode.SelectSingleNode("Required").InnerText);
                AllTransports.Add(new Transport(Name, Capacity, Weight, Speed, Required));
            }
        }
        private static void loadTowns()
        {
            Towns = new List<Town>();
            XmlDocument townsXML = new XmlDocument();
            townsXML.Load(@"data\Towns.xml");
            XmlNode townsRoot = townsXML.DocumentElement;
            foreach (XmlNode townNode in townsRoot.ChildNodes)
            {
                if (townNode.NodeType == XmlNodeType.Comment)
                    continue;
                Towns.Add(
                    new Town(townNode.Attributes["Name"].Value, loadItems(townNode.SelectSingleNode("Items")), parsePoint(townNode.SelectSingleNode("Map/Location").InnerText)));
            }
            foreach (Town t in Towns)
            {
                foreach (Item i in t.Items)
                {
                    foreach (Town targetTown in Towns.Where(new Func<Town, bool>((testTown) => { return testTown != t; })))
                    {
                        i.Profits.Add(targetTown, 0);
                    }
                }
            }
        }
        private static List<Item> loadItems(XmlNode itemsNode)
        {
            List<Item> items = new List<Item>();
            foreach (XmlNode itemEntry in itemsNode.ChildNodes)
            {
                if (itemEntry.NodeType == XmlNodeType.Comment)
                    continue;
                items.Add(new Item(itemEntry.Attributes["Name"].Value,
                    int.Parse(itemEntry.Attributes["Weight"].Value),
                    int.Parse(itemEntry.Attributes["MaxQuantity"].Value),
                    int.Parse(itemEntry.Attributes["Rating"].Value)));
            }
            return items;
        }
        private static void loadPaths()
        {
            Paths = new List<Path>();
            XmlDocument pathsXML = new XmlDocument();
            pathsXML.Load(@"data\Routes.xml");
            XmlNode pathsRoot = pathsXML.DocumentElement;
            foreach (XmlNode pathNode in pathsRoot.ChildNodes)
            {
                if (pathNode.NodeType == XmlNodeType.Comment)
                    continue;

                string name = pathNode.Attributes["Name"].Value;

                int routeTime = int.Parse(pathNode.Attributes["Time"].Value);
                Town t1, t2;

                //Hacky way of getting the two destination towns
                t1 = Towns.First(new Func<Town, bool>((testTown) =>
                    {
                        return testTown.Name.Equals(pathNode.ChildNodes[0].ChildNodes[0].Attributes["Name"].Value);
                    }
                )); 
                t2 = Towns.First(new Func<Town, bool>((testTown) =>
                    {
                        return testTown.Name.Equals(pathNode.ChildNodes[0].ChildNodes[1].Attributes["Name"].Value);
                    }
                ));

                XmlNode MapNode = pathNode.SelectSingleNode("Map");
                Point MapLoc, SplitLeft, SplitRight;
                MapLoc = parsePoint(MapNode.SelectSingleNode("Location").InnerText);
                SplitLeft = parsePoint(MapNode.SelectSingleNode("SplitLeft").InnerText);
                SplitRight = parsePoint(MapNode.SelectSingleNode("SplitRight").InnerText);

                Path p = new Path(name, t1, t2, routeTime, MapLoc, SplitLeft, SplitRight);
                t1.Paths.Add(p);
                t2.Paths.Add(p);
                Paths.Add(p);
            }
        }
        private static void calculateRoutes()
        {
            Routes = new List<Route>();
            for (int i = 0; i < Towns.Count-1; i++)
            {
                for (int j = i+1; j < Towns.Count; j++)
                {
                    if (alreadyCalculated(Towns[i], Towns[j]))
                        continue;
                    Routes.Add(calculateRoute(Towns[i], Towns[j]));
                }
            }
        }
        private static bool alreadyCalculated(Town t1, Town t2)
        {
            foreach (Route r in Routes)
            {
                if (r.EndTowns.Contains(t1) && r.EndTowns.Contains(t2))
                    return true;
            }
            return false;
        }

        private static Route calculateRoute(Town t1, Town t2)
        {
            List<Town> waypoints = new List<Town>();
            return calcRoutePortion(t1, t2, waypoints);
        }

        private static Route calcRoutePortion(Town source, Town destination, List<Town> visited)
        {
            if (source == destination)
                return new Route(null, null, 0, new Town[0]);

            visited.Add(source);

            Route bestRoute = new Route(null, null, int.MaxValue, new Town[0]);

            foreach (Path p in source.Paths)
            {
                if (visited.Contains(p.GetOtherTown(source)))
                    continue;
                List<Town> visitedCopy = new List<Town>();
                foreach (Town t in visited)
                    visitedCopy.Add(t);

                Route candidate = calcRoutePortion(p.GetOtherTown(source), destination, visitedCopy);
                if (candidate == null)
                    continue;
                if (candidate.TotalTime == 0)
                {
                    bestRoute = new Route(source, destination, p.Length, new Town[0]);
                    break;
                }
                else if (candidate.TotalTime + p.Length < bestRoute.TotalTime)
                {
                    candidate.Waypoints.Reverse();
                    candidate.Waypoints.Add(p.GetOtherTown(source));
                    candidate.Waypoints.Reverse();
                    bestRoute = new Route(source, destination, candidate.TotalTime + p.Length, candidate.Waypoints.ToArray());
                }
            }
            if (bestRoute.TotalTime == int.MaxValue)
                return null;
            else
                return bestRoute;
        }

        //private static Route calcRoutePortion(Town center, Town destination, List<Town> visited)
        //{
        //    if (center == destination)
        //        return new Route(center, destination, 0, new List<Town>().ToArray());
        //    visited.Add(center);

        //    Route bestRoute = new Route(null, null, Int32.MaxValue, new List<Town>().ToArray());

        //    foreach (Path p in center.Paths)
        //    {
        //        if (visited.Contains(p.GetOtherTown(center)))
        //            continue;
        //        List<Town> tempVisted = new List<Town>();
        //        foreach (Town t in visited)
        //            tempVisted.Add(t);
        //        Route routePart = calcRoutePortion(p.GetOtherTown(center), destination, tempVisted);
        //        if (routePart != null && routePart.TotalTime + p.Length < bestRoute.TotalTime )
        //        {
        //            if (routePart.TotalTime != 0)
        //            {
        //                routePart.Waypoints.Add(p.GetOtherTown(center));
        //            }
        //            routePart.Waypoints.Reverse();
        //            bestRoute = new Route(center, destination, routePart.TotalTime + p.Length, routePart.Waypoints.ToArray());
        //        }
        //    }
        //    if (bestRoute.TotalTime != Int32.MaxValue)
        //        return bestRoute;
        //    else
        //        return null;
        //}
    }
}